package com.ny.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ny.entity.SensorData;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.UUID;

/**
 * MQTT消息服务类：封装与阿里云物联网平台的MQTT通信逻辑
 * 实现MqttCallback接口，处理连接状态和消息事件
 */
@Service // 标记为Spring服务组件，交由容器管理
public class MqttMessageService implements MqttCallback {

    // 日志组件：用于打印系统运行日志
    private static final Logger logger = LoggerFactory.getLogger(MqttMessageService.class);

    // 从配置文件注入阿里云MQTT服务器连接参数
    @Value("${aliyun.mqtt.host}")
    private String host; // MQTT服务器主机地址

    @Value("${aliyun.mqtt.port}")
    private int port; // MQTT服务器端口

    @Value("${aliyun.mqtt.clientId}")
    private String clientId; // 客户端唯一标识

    @Value("${aliyun.mqtt.username}")
    private String username; // 连接认证用户名

    @Value("${aliyun.mqtt.password}")
    private String password; // 连接认证密码

    @Value("${aliyun.mqtt.subscribe.topic}")
    private String subscribeTopic; // 订阅主题（接收消息）

    @Value("${aliyun.mqtt.publish.topic}")
    private String publishTopic; // 发布主题（发送消息）

    // 存储最新的传感器数据（温度、湿度、LED状态）
    private SensorData sensorData;

    // 标记MQTT客户端是否已连接
    private boolean isConnected = false;

    // MQTT客户端实例：用于与服务器建立连接和收发消息
    private MqttClient client;

    // 线程锁：用于同步修改传感器数据，避免多线程并发问题
    private final Object lock = new Object();

    // MQTT服务器完整连接地址（格式：tcp://host:port）
    private String brokerUrl;

    /**
     * 初始化方法：项目启动时自动执行（@PostConstruct注解）
     * 作用：创建MQTT客户端、建立连接、订阅主题
     */
    @PostConstruct
    public void init() {
        try {
            // 构建MQTT服务器连接地址
            this.brokerUrl = "tcp://" + host + ":" + port;

            // 初始化传感器数据对象
            sensorData = new SensorData();

            // 创建MQTT客户端实例：
            // 参数1：服务器连接地址
            // 参数2：客户端ID（需保证唯一）
            // 参数3：消息持久化方式（MemoryPersistence表示内存存储，不落地）
            client = new MqttClient(brokerUrl, clientId, new MemoryPersistence());

            // 设置回调处理器：当前类实现了MqttCallback接口，处理连接断开、消息到达等事件
            client.setCallback(this);

            // 配置MQTT连接参数
            MqttConnectOptions options = new MqttConnectOptions();
            options.setUserName(username); // 设置认证用户名
            options.setPassword(password.toCharArray()); // 设置认证密码

            // 连接MQTT服务器
            client.connect(options);
            logger.info("成功连接到 MQTT 服务器：{}", brokerUrl);
            isConnected = true;

            // 订阅主题：连接成功后订阅指定主题，准备接收消息
            client.subscribe(subscribeTopic);

        } catch (MqttException e) {
            // 连接失败时记录错误日志
            logger.error("连接 MQTT 服务器失败：{}", e.getMessage(), e);
            isConnected = false;
        }
    }

    /**
     * 连接丢失回调方法：当MQTT连接意外断开时触发
     * @param cause 连接丢失的原因（异常对象）
     */
    @Override
    public void connectionLost(Throwable cause) {
        logger.warn("MQTT 连接丢失：{}", cause.getMessage());
        // 触发重连逻辑
        reconnect();
    }

    /**
     * 重连方法：连接丢失后尝试重新连接服务器并订阅主题
     */
    private void reconnect() {
        // 若客户端为空或已连接，则无需重连
        if (client == null || client.isConnected()) {
            return;
        }

        try {
            client.connect(); // 重新连接服务器
            client.subscribe(subscribeTopic); // 重新订阅主题
            logger.info("MQTT 已重新连接并订阅主题：{}", subscribeTopic);
        } catch (MqttException e) {
            logger.error("MQTT 重连失败", e);
        }
    }

    /**
     * 消息到达回调方法：当订阅的主题有新消息时触发
     * @param topic 消息所属的主题
     * @param message 收到的MQTT消息对象
     * @throws Exception 消息处理异常
     */
    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
        // 将消息内容（字节数组）转为字符串
        String payload = new String(message.getPayload());
        logger.info("收到来自主题[{}]的消息：{}", topic, payload);

        // 使用Jackson解析JSON格式的消息内容
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = objectMapper.readTree(payload);

        // 提取消息中的"params"字段（设备属性参数）
        JsonNode paramsNode = rootNode.get("params");

        // 校验消息是否包含所需的属性（temperature、humi、led）
        if (paramsNode != null && paramsNode.has("temperature")
                && paramsNode.has("humi") && paramsNode.has("led")) {

            // 加锁更新传感器数据，保证多线程环境下的数据一致性
            synchronized (lock) {
                sensorData.setTemperature(paramsNode.get("temperature").asDouble()); // 温度（double类型）
                sensorData.setHumi(paramsNode.get("humi").asInt()); // 湿度（int类型）
                sensorData.setLed(paramsNode.get("led").asInt()); // LED状态（int类型，0=关闭，1=开启）
            }

            // 打印更新后的传感器数据
            logger.info("传感器数据更新：{}", sensorData);
            logger.info("温度：{}℃，湿度：{}%，LED状态：{}",
                    sensorData.getTemperature(),
                    sensorData.getHumi(),
                    sensorData.getLed() == 1 ? "开启" : "关闭");
        } else {
            logger.warn("消息格式异常，未找到所需属性（temperature/humi/led）");
        }
    }

    /**
     * 消息发布完成回调方法：当消息成功发布到服务器后触发（仅QoS 1/2有效）
     * @param token 消息发布的令牌（包含发布相关信息）
     */
    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        // 本示例使用QoS 1，若需确认消息是否发布成功，可在此处处理
        // 例如：logger.info("消息发布完成，消息ID：{}", token.getMessageId());
    }

    /**
     * 发布单个属性消息到MQTT服务器
     * @param propertyName 属性名（如"temperature"、"humi"、"led"）
     * @param value 属性值（支持数字、布尔类型）
     */
    public void setProperty(String propertyName, Object value) {
        // 校验客户端状态：未初始化或未连接则直接返回
        if (client == null || !client.isConnected()) {
            logger.warn("MQTT 客户端未连接，无法发布消息");
            return;
        }

        try {
            // 使用Jackson构建JSON格式的消息体
            ObjectMapper mapper = new ObjectMapper();
            ObjectNode rootNode = mapper.createObjectNode();

            // 设置消息指令类型：阿里云物联网平台要求的"设备属性设置"方法
            rootNode.put("method", "thing.service.property.set");
            // 设置消息唯一ID（UUID生成，用于匹配请求与响应）
            rootNode.put("id", UUID.randomUUID().toString());

            // 构建属性参数节点
            ObjectNode paramsNode = rootNode.putObject("params");
            // 支持数字（int/double等）和布尔类型的属性值
            if (value instanceof Number || value instanceof Boolean) {
                paramsNode.putPOJO(propertyName, value); // 自动匹配值类型
            } else {
                throw new IllegalArgumentException("不支持的属性值类型: " + value.getClass());
            }

            // 转换JSON对象为字节数组，构建MQTT消息
            MqttMessage mqttMessage = new MqttMessage(mapper.writeValueAsBytes(rootNode));

            // 发布消息到指定主题：
            // 参数1：发布主题
            // 参数2：消息内容字节数组
            // 参数3：QoS等级（1=至少一次送达）
            // 参数4：是否保留消息（false=不保留，仅当前订阅者可见）
            client.publish(publishTopic, mqttMessage.getPayload(), 1, false);
            logger.info("成功发布属性[{}={}]到主题[{}]", propertyName, value, publishTopic);

        } catch (MqttException | JsonProcessingException e) {
            logger.error("发布MQTT消息失败", e);
        }
    }

    /**
     * 批量发布属性消息到MQTT服务器
     * @param properties 属性键值对映射（key=属性名，value=属性值）
     */
    public void setProperties(Map<String, Object> properties) {
        // 遍历属性映射，逐个发布
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            setProperty(entry.getKey(), entry.getValue());
        }
    }

    /**
     * 获取MQTT客户端连接状态
     * @return 连接状态（true=已连接，false=未连接）
     */
    public boolean isConnected() {
        return isConnected;
    }

    /**
     * 获取最新的传感器数据（线程安全）
     * @return 传感器数据对象（包含温度、湿度、LED状态）
     */
    public SensorData getSensorData() {
        synchronized (lock) { // 加锁保证读取时数据不被修改
            return sensorData; // 实际应用中建议返回副本（如new SensorData(sensorData)），避免外部修改
        }
    }

    /**
     * 销毁方法：项目关闭时自动执行（@PreDestroy注解）
     * 作用：断开MQTT连接，释放资源
     */
    @PreDestroy
    public void destroy() {
        try {
            if (client != null && client.isConnected()) {
                client.disconnect(); // 断开连接
                logger.info("MQTT 客户端已断开连接");
            }
        } catch (MqttException e) {
            logger.error("MQTT 断开连接失败", e);
        }
    }
}